// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package keystore

import (
	"sync"
)

// pendingChanges are changes which are expected to be eventually observed in the API keystore.
type pendingChanges struct {
	// changes maps for each Alias (remote cluster name as defined in the client ES) the pending change.
	changes map[string]pendingChange
	mu      sync.RWMutex
}

type pendingChange struct {
	remoteClusterName, remoteClusterNamespace, alias string
	key                                              key
}

// key holds an expected key as generated by Elasticsearch, with its ID and its encoded value.
type key struct {
	keyID, encodedValue string
}

// AddKey registers a key to be eventually persisted in the underlying Secret.
func (pc *pendingChanges) AddKey(remoteClusterName, remoteClusterNamespace, alias, keyID, encodedKeyValue string) {
	if pc == nil {
		return
	}
	pc.mu.Lock()
	defer pc.mu.Unlock()
	pc.changes[alias] = pendingChange{
		remoteClusterName:      remoteClusterName,
		remoteClusterNamespace: remoteClusterNamespace,
		alias:                  alias,
		key: key{
			keyID:        keyID,
			encodedValue: encodedKeyValue,
		},
	}
}

// ForgetChangeFor must be called once we know that the key added with AddKey or removed with DeleteAlias is reflected in the underlying Secret.
func (pc *pendingChanges) ForgetChangeFor(alias string) {
	if pc == nil {
		return
	}
	pc.mu.Lock()
	defer pc.mu.Unlock()
	delete(pc.changes, alias)
}

// DeleteAlias registers the deletion of the key for the provided alias.
func (pc *pendingChanges) DeleteAlias(alias string) {
	if pc == nil {
		return
	}
	pc.mu.Lock()
	defer pc.mu.Unlock()
	pc.changes[alias] = pendingChange{
		alias: alias,
		key:   key{}, // an empty key means that this alias must be deleted
	}
}

// Get returns all the pending changes.
func (pc *pendingChanges) Get() []pendingChange {
	if pc == nil {
		return nil
	}
	pc.mu.RLock()
	defer pc.mu.RUnlock()
	pendingChanges := make([]pendingChange, 0, len(pc.changes))
	for alias, change := range pc.changes {
		pendingChange := pendingChange{
			remoteClusterName:      change.remoteClusterName,
			remoteClusterNamespace: change.remoteClusterNamespace,
			alias:                  alias,
			key: key{
				keyID:        change.key.keyID,
				encodedValue: change.key.encodedValue,
			},
		}
		pendingChanges = append(pendingChanges, pendingChange)
	}
	return pendingChanges
}

func (k *key) IsEmpty() bool {
	if k == nil {
		return true
	}
	return k.encodedValue == "" && k.keyID == ""
}
